Displaying Image Thumbnails

Latest update: August 2013

In this tutorial, we will show you how to get thumbnail images of files on your FlashAir. We will use thumbnail.cgi and command.cgi to do this. This tutorial builds off of Android Tutorial 3: Downloading Content.

We will pair file names with their corresponding thumbnail images and show them in an interactive content list. Upon starting the application, the root directory will be displayed. The list will look like this:

This image shows a content list of the root directory

If you click an image file in the content list, it should display the image in another screen:

This image shows image view

Upon clicking on a directory in the content list, that child directory will open and display its contents:

This image shows a content list of a child directory

If you click an image file in the child directory, it should display the image in another screen:

This image shows image view

As you are viewing the image, the image file will download to your Android device.

This tutorial will take some of the commands learned in previous tutorials and use them to obtain or display data.

We will need to create the following files in order to write this application:

  • MainActivity.java
  • activity_main.xml
  • ImageViewActivity.java
  • activity_image_view.xml

Important: Please note that your project contains a file called AndroidManifest.xml. This file gives your application particular permissions. By default, applications are not permitted to access the internet. The path to this file should look something like: [Project_Folder]/AndroidManifest.xml
You will need to add the following lines of code into your AndroidManifest.xml in order for this application to work:

<uses-permission android:name="android.permission.INTERNET" />

Creating the List Layout

First, we will write the activity_main.xml file that determines the layout of our Android App. This can be found in your layout folder. The path to this file should look something like: [Project_Folder]/res/layout/activity_main.xml

This file will be identical to the activity_main.xml file from Android Tutorial 3: Downloading Content. Please refer to that tutorial for an explanation of the implementation.

Creating the Image Viewing Layout

Next, we will create the activity_image_view.xml file that determines the layout of our image viewing screen. This can also be found in your layout folder. The path to this file should look something like: [Project_Folder]/res/layout/activity_image_view.xml

This file will be identical to the activity_image_view.xml file from Android Tutorial 3: Downloading Contents. Please refer to that tutorial for an explanation of the implementation.

Creating the Content List

Now we will modify the MainActivity.java file. We will start by getting a list of the file names in the desired directory of your FlashAir device (see Android Tutorial 3: Downloading Contents for a detailed explanation). Then we will get a thumbnail image for each file in the list and will set the list to display both the file name and thumbnail pair in a ListView format. We will then establish the click behavior of the list.

Initialization

The content list should not only be clickable, but specifically be able to recognize which element in the list is being clicked and subsequently have that item display itself. We will have to implement our own version of the onItemClick() function to do this, so we will start by modifying the class declaration. We want our content list to appear when the application opens, so we will also modify the onCreate(Bundle savedInstanceState) function that initializes the Activity class.

Although this code is very similar to that in previous tutorials, there are some critical differences. If you are reusing code from the previous tutorial, please be sure to check that your variable types and function calls match this new tutorial code. We will write the reason using SimpleAdapter for this function later in this tutorial.

MainActivity.java (1)

public class MainActivity extends Activity implements AdapterView.OnItemClickListener {

    ListView listView;
    ImageView imageView;
    TextView currentDirText;
    TextView numFilesText;
    Button backButton;
    String rootDir = "DCIM";
    String directoryName = rootDir; // Initialize to rootDirectory
    SimpleAdapter listAdapter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        try {
            // Set buttons
            getWindow().setTitleColor(Color.rgb(65, 183, 216));
            backButton = (Button)findViewById(R.id.button1);
            backButton.getBackground().setColorFilter(Color.rgb(65, 183, 216), PorterDuff.Mode.SRC_IN);
            backButton.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    if(directoryName.equals(rootDir)) {
                        listRootDirectory();
                    }
                    else {
                        int index = directoryName.lastIndexOf("/");
                        directoryName = directoryName.substring(0, index);
                        listDirectory(directoryName);
                    }
                }
            });
            backButton.setEnabled(false); // Disable in root directory
            listRootDirectory();
        }
        catch(Exception e) {
            Log.e("ERROR", "ERROR: " + e.toString());
            e.printStackTrace();
        }
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        // Inflate the menu; this adds items to the action bar if it is present.
        getMenuInflater().inflate(R.menu.main, menu);
        return true;
    }

Creating a ViewBinder

We are using a ListView to view our thumbnail and file name pairs. However, since we are fetching each image from the FlashAir device, and do not have it as a static Drawable in our project folder, our application will have to process each thumbnail image at runtime. We will have to use an Adapter to handle the thumbnail images. Since we need the thumbnail images to be associated with particular file names, we have chosen to use a SimpleAdapter, which is declared above. We need to create a custom ViewBinder so that the SimpleAdapter will correctly map the thumbnails and file names to their respective views.

MainActivity.java (2)

    class CustomViewBinder implements ViewBinder {
        @Override
        public boolean setViewValue(View view, Object obj, String text) {
            if((view instanceof ImageView) && (obj instanceof Drawable)) {
                ImageView imageView = (ImageView) view;
                BitmapDrawable thumbnail = (BitmapDrawable) obj;
                imageView.setImageDrawable((Drawable)thumbnail);
                return true;
            }
            return false;
        }
    }

Making a Content List for a Directory

We will use the following CGI command to get the number of items in each directory:

  • command.cgi with op=101 and the directory as a parameter
    • The command will look like this: http://flashair/command.cgi?op=101&DIR=/DCIM
    • The command will return the following information about the directory: <NumberofItems>

We will use the following command to retrieve a list of contents from the FlashAir device:

  • command.cgi with op=100 and the directory as a parameter
    • The command will look like this: http://flashair/command.cgi?op=100&DIR=/DCIM
    • The command will return the following information about each item in the directory: <Directory>,<Filename>,<Size>,<Attribute>,<Date>,<Time>

We will use the following CGI command to get the thumbnail of each image file:

  • thumbnail.cgi with the file path as a parameter
    • The command will look like this: http://flashair/thumbnail.cgi?directoryPath/fileName
    • The command will return the following data: <EXIF_thumbnail_image>

We will set each thumbnail to be a value for key "thmb" and each file name to be a value for key "fname"in a Map. This Map will be added to an ArrayList, which will be used by the SimpleAdapter above. We will read the file into a Bitmap object.

To execute this CGI command, we will reuse the FlashAirRequest.java file from Android Tutorial 3: Downloading Contents:

The listRootDirectory() and listDirectory(directoryName) functions called in the onCreate(Bundle savedInstanceState) function are the functions that handle requesting and fetching content data from the FlashAir device and displaying it to the screen. The implementation for these functions are explained in Android Tutorial 3: Downloading Contents. Please refer to that tutorial for additional detail.

MainActivity.java (3)

    public void listRootDirectory() {
        directoryName = rootDir;
        listDirectory(directoryName);
    }

    public void listDirectory(String dir) {
        // Prepare command directory path
        if(dir.equals(rootDir)) {
            backButton.setEnabled(false);
        }
        else {
            backButton.setEnabled(true);
        }
        currentDirText = (TextView)findViewById(R.id.textView1);
        currentDirText.setText(dir + "/");
        dir = "/" + dir;
        ArrayList <NameValuePair> httpParams = new  ArrayList <NameValuePair> ();
        httpParams.add(new BasicNameValuePair("DIR", dir));
        dir = URLEncodedUtils.format (httpParams, "UTF-8" );
        numFilesText = (TextView)findViewById(R.id.textView2);                
        // Fetch number of items in directory and display in a TextView
        new AsyncTask<String, Void, String>(){
            @Override
            protected String doInBackground(String... params) {
                String dir = params[0];
                String fileCount =  FlashAirRequest.getString("http://flashair/command.cgi?op=101&" + dir);                

                return fileCount;
            }
            @Override
            protected void onPostExecute(String fileCount) {
                numFilesText.setText("Items Found: " + fileCount);                
            }
        }.execute(dir);    
        // Fetch list of items in directory and display in a ListView
        new AsyncTask<String, Void, ListAdapter>(){
            @Override
            protected ListAdapter doInBackground(String... params) {
                String dir = params[0];
                ArrayList <String> fileNames = new ArrayList <String>();                
                String files = FlashAirRequest.getString("http://flashair/command.cgi?op=100&" + dir);
                String[] allFiles = files.split("([,\n])"); // split by newline or comma
                for(int i = 2; i < allFiles.length; i= i + 6) {
                    if(allFiles[i].contains(".")) {
                        // File
                        fileNames.add(allFiles[i]);
                    }
                    else { // Directory, append "/"
                        fileNames.add(allFiles[i] + "/");
                    }
                }                    

                // Get thumbnails
                ArrayList<Map<String, Object>> data = new ArrayList<Map<String, Object>>();
                for(int i = 0; i < fileNames.size(); i++) {
                    String fileName = "";
                    fileName = "http://flashair/thumbnail.cgi?" + directoryName + "/" + fileNames.get(i);
                    Map<String, Object> entry = new HashMap<String, Object>();
                    BitmapDrawable drawnIcon = null;
                    if( (fileName.toLowerCase(Locale.getDefault()).endsWith(".jpg")) || (fileName.toLowerCase(Locale.getDefault()).endsWith(".jpeg")) ) {
                        Bitmap thumbnail = FlashAirRequest.getBitmap(fileName);
                        drawnIcon = new BitmapDrawable(getResources(), thumbnail);
                    }
                    if(drawnIcon == null) {
                        entry.put("thmb", R.drawable.ic_launcher);
                    }
                    else {
                        entry.put("thmb", drawnIcon);
                    }
                    entry.put("fname", fileNames.get(i)); // Put file name onto the map
                    data.add(entry);
                }

                // Set the file list to a widget
                listAdapter = new SimpleAdapter(MainActivity.this,
                        data,
                        android.R.layout.activity_list_item,
                        new String[]{"thmb", "fname"},
                        new int[]{android.R.id.icon, android.R.id.text1});
            listAdapter.setViewBinder(new CustomViewBinder());

                return listAdapter;
            }
            @Override
            protected void onPostExecute(ListAdapter listAdapter) {
                listView = (ListView)findViewById(R.id.listView1);
                ColorDrawable divcolor = new ColorDrawable(Color.rgb(17, 19, 58));
                listView.setDivider(divcolor);
                listView.setDividerHeight(1);
                listView.setAdapter(listAdapter);
                listView.setOnItemClickListener(MainActivity.this);                
            }
    }.execute(dir);    

}

Note that the thumbnail.cgi command works exclusively on JPEG files that have EXIF data.

  • Line 60:
    We check each file to determine whether it is a JPEG.
    Once a file is determined to be a JPEG, we check for the presence of EXIF data (line 30). We determine the presence of EXIF data by testing if the BitmapDrawable that we saved the thumbnail to is null. If this test returns null, we assume that the JPEG did not have EXIF data.
  • Lines 64-66:
    We set non-JPEG files and JPEG files that do not contain EXIF data to display the Android icon (provided in your default Android Application Project) as their associated thumbnail image.
  • Lines 86-91:
    Set up the new ListView. This is where the thumbnail and file name data are bound to their respective views. This code also identifies the particular layout elements that the data needs to be sent to.
  • Lines 75-80:
    We instantiate our new Adapter and CustomViewBinder. These are bound to the ListView in lines 90.

Setting the Item Click Listener

We will set the list behavior to be the same as it was in Android Tutorial 3: Downloading Contents:

  • If the item clicked is the name of a folder, the next screen will display the contents of the folder.
  • If the item clicked is the name of an image file, that file will be downloaded, and the next screen will display its image.

Although the behavior of the list will remain the same, the list has been formatted differently. (The Adapter we use for the ListView is now composed of a list of Map objects.)

We will override the original onItemClick() function to account for this change:

MainActivity.java (4)

    @Override
    public void onItemClick(AdapterView<?> l, View v, int position, long id) {
        Object item = l.getItemAtPosition(position); // Get item at clicked position in list of files
        if(item instanceof Map<?, ?>) {
            Map<String, Object> mapItem = (Map<String, Object>) item;
            Object downloadFile = mapItem.get("fname");
            if(downloadFile.toString().endsWith("/")) {
                // Directory, remove "/" and show content list
                String dirName = downloadFile.toString().substring(0, downloadFile.toString().length()-1); // All but the "/"
                directoryName = directoryName + "/" + dirName;
                listDirectory(directoryName);
            }
            else if( (downloadFile.toString().toLowerCase(Locale.getDefault()).endsWith(".jpg")) || (downloadFile.toString().toLowerCase(Locale.getDefault()).endsWith(".jpeg"))
                    || (downloadFile.toString().toLowerCase(Locale.getDefault()).endsWith(".jpe")) || (downloadFile.toString().toLowerCase(Locale.getDefault()).endsWith(".png")) ) {
                // Image file, download using ImageViewActivity
                Intent viewImageIntent = new Intent(this, ImageViewActivity.class);
                viewImageIntent.putExtra("downloadFile", downloadFile.toString());
                viewImageIntent.putExtra("directoryName", directoryName);
                MainActivity.this.startActivity(viewImageIntent);
            }
        }
    }
} // End MainActivity class

Creating the Image Viewing Screen

This file will be identical to the class ImageViewActivity from Android Tutorial 3: Downloading Contents. Please refer to that tutorial for an explanation of the implementation.

Sample Code

android_tutorial_04.zip (533KB)

All sample code on this page is licensed under BSD 2-Clause License